﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace SimpleIR
{
	/// <summary>
	/// This class represents any call operation (virtual, static, or via a function pointer).
	/// </summary>
	public sealed class IRCallOp : IRByteCode
	{
		/// <summary>
		/// The declared return type of the call.
		/// </summary>
		private readonly IRType m_restype;

		/// <summary>
		/// If this is a CALL (CALLVIRT) then this is the exact (restricted) type for the call (null otherwise).
		/// </summary>
		private readonly IRType m_specificTarget;

		/// <summary>
		/// The type that is provided by the .net compiler for the type that defines the method 
		/// (i.e. introduces the definition). 
		/// Generally the <c>m_reciverexptype</c> we extract below is more precise but this is useful 
		/// when resolving calls with explicit implementations.
		/// </summary>
		private readonly IRType m_baseimpltype;

		/// <summary>
		/// If this is a virtual method then this is the type of the this parameter (null otherwise).
		/// </summary>
		private readonly IRType m_reciverexptype;
		
		/// <summary>
		/// The signature of the invocation.
		/// </summary>
		private readonly IRInvokeSig m_invs;

		private IRCallOp(OpCode.Op copc, IRType rtype, IRType baseimpltype, IRType reciverType, IRInvokeSig sig, IRType specTarget, IRSourceLocation sloc)
			: base(copc, sloc)
		{
			this.m_restype = rtype;
			this.m_baseimpltype = baseimpltype;
			this.m_reciverexptype = reciverType;
			this.m_invs = sig;
			this.m_specificTarget = specTarget;
		}

		/// <summary>
		/// Make a call to a static method. 
		/// </summary>
		public static IRCallOp MakeStaticMethodCall(IRType rtype, IRStaticInvoke sig, IRSourceLocation sloc)
		{ return new IRCallOp(OpCode.Op.Call, rtype, null, null, sig, sig.DefClass, sloc); }

		/// <summary>
		/// Make a virtual method call on a reciver argument of the given type and specialized to specTarget.
		/// </summary>
		public static IRCallOp MakeRestrictedVirtualCall(IRType rtype, IRType baseimpltype, IRType reciverType, IRVirtualInvoke sig, IRType specTarget, IRSourceLocation sloc)
		{ return new IRCallOp(OpCode.Op.Call, rtype, baseimpltype, reciverType, sig, specTarget, sloc); }

		/// <summary>
		/// Make a virtual method call on a reciver argument of the given type.
		/// </summary>
		public static IRCallOp MakeVirtualCall(IRType rtype, IRType baseimpltype, IRType reciverType, IRVirtualInvoke sig, IRSourceLocation sloc)
		{ return new IRCallOp(OpCode.Op.CallVirt, rtype, baseimpltype, reciverType, sig, null, sloc); }

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder();

			if(this.OperationCode == OpCode.Op.CallVirt)
				sb.Append(this.m_reciverexptype.ToString() + "@" + this.m_baseimpltype.ToString() + "." + this.m_invs.ToString());
			else
				sb.Append("(" + this.m_specificTarget.ToString() + ")" + this.m_invs.ToString());

			return sb.ToString();
		}

		/// <summary>
		/// Return true if the method has a specified target class to lookup the method definition in.
		/// </summary>
		public bool HasSpecificTarget 
		{ get { return this.m_specificTarget != null; } }

		/// <summary>
		/// If the method has a specified target class to lookup the method definition in return the type.
		/// </summary>
		public IRType SpecificTargetClass 
		{ 
			get 
			{
				Debug.Assert(this.HasSpecificTarget);
				return this.m_specificTarget; 
			} 
		}

		/// <summary>
		/// If the call is a virtual invoke get the base type as given by the .net compiler 
		/// which defines the desired method.
		/// </summary>
		public IRType BaseImplementationType
		{
			get
			{
				Debug.Assert(this.InvokeSig is IRVirtualInvoke);
				return this.m_baseimpltype;
			}
		}

		/// <summary>
		/// If the call is a virtual invoke get the type of the reciver.
		/// </summary>
		public IRType ReciverExpType 
		{ 
			get 
			{
				Debug.Assert(this.InvokeSig is IRVirtualInvoke);
				return this.m_reciverexptype; 
			} 
		}

		/// <summary>
		/// Get the return type for the method call.
		/// </summary>
		public IRType ReturnType
		{ get { return this.m_restype; } }
		
		/// <summary>
		/// Return the signature for the method that is being invoked.
		/// </summary>
		public IRInvokeSig InvokeSig
		{ get { return this.m_invs; } }
	}
}
